package com.longjoe.ljframework.net.client;

import com.alibaba.fastjson.JSON;
import com.longjoe.ljframework.net.combase.ILJRequest;
import com.longjoe.ljframework.net.combase.LJResponse;
import com.longjoe.ljframework.util.FileUtils;
import com.longjoe.ljframework.util.Logger;

import android.os.Build;
import android.support.annotation.NonNull;
import android.text.TextUtils;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

/**
 * Created by Heycz on 2015/5/14.
 */
public class P1Client implements ILJClient {

	public final String TAG = "P1Client";
	public final String DEFAULT_CHARSET = "UTF-8";
	public final byte PROTOCOL_FLAG = 0x04;//00000100 协议标记
	public final byte INPUT_GZIP = 0x01;//00000001 压缩输入流
	public final byte OUTPUT_GZIP = 0x02;//00000010 压缩输出流
	public String charset = DEFAULT_CHARSET;
	private Socket socket;

	public P1Client(Socket socket) {
		this.socket = socket;
	}

	@NonNull
	@Override
	public <T extends LJResponse> T doExecute(ILJRequest<T> request, Class<T> cls) {
		return doExecute(request, cls, null);
	}

	@NonNull
	@Override
	public <T extends LJResponse> T doExecute(ILJRequest<T> request, Class<T> cls, Map<String, String> files) {
		try {
			OutputStream os = socket.getOutputStream();
			if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
				os.write(PROTOCOL_FLAG | INPUT_GZIP | OUTPUT_GZIP);
			} else {
				os.write(PROTOCOL_FLAG | OUTPUT_GZIP);
			}
			GZIPOutputStream oZip = new GZIPOutputStream(os);
			DataOutputStream outputStream = new DataOutputStream(oZip);

			byte[] jsonData = (request.GetApiName() + JSON.toJSONString(request)).getBytes(charset);
			byte[] jsonDataLength = intToByteArray(jsonData.length);
			outputStream.write(jsonDataLength);//写入JSON长度
			outputStream.write(jsonData);//写入JSON
			outputStream.flush();
			if (files != null) {
				outputStream.write(shortToByteArray(files.size()));//写入文件个数
				for (Map.Entry<String, String> entry : files.entrySet()) {
					byte[] fileName = entry.getKey().getBytes(charset);
					byte[] fileNameLength = intToByteArray(fileName.length);
					outputStream.write(fileNameLength);//写入文件名长度
					outputStream.write(fileName);//写入文件名
					outputStream.flush();
					File file = new File(entry.getValue());
					outputStream.write(intToByteArray((int) file.length()));//写入文件长度
					FileInputStream fileInputStream = new FileInputStream(file);
					final byte[] tmp = new byte[4096];
					int bytesRead;
					while ((bytesRead = fileInputStream.read(tmp)) != -1) {
						outputStream.write(tmp, 0, bytesRead);//写入文件内容
					}
					outputStream.flush();
				}
			} else {
				outputStream.writeShort(0);//写入文件个数
				outputStream.flush();
			}
			oZip.finish();

			InputStream is = socket.getInputStream();

			DataInputStream inputStream;
			//TODO 5.0GZIPInputStream存在bug,待修复后恢复
			if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
				GZIPInputStream iZip = new GZIPInputStream(is);
				inputStream = new DataInputStream(iZip);
			} else {
				inputStream = new DataInputStream(is);
			}

			byte[] intBytes = new byte[4];
			inputStream.readFully(intBytes);
			int len = byteArrayToInt(intBytes);//读取response长度
			byte[] bytes = new byte[len];
			inputStream.readFully(bytes);//读取response

			String responseStr = new String(bytes, charset);//字节转码
			LJResponse response = JSON.parseObject(responseStr, cls);

			List<File> fileList = null;
			byte[] tmp = new byte[1];
			if (inputStream.read(tmp) != -1) {
				byte cnt = tmp[0];
				for (int i = 0; i < cnt; i++) {
					inputStream.readFully(intBytes);
					int nameLen = byteArrayToInt(intBytes);
					byte[] nameBytes = new byte[nameLen];
					inputStream.readFully(nameBytes);
					String name = new String(nameBytes, charset).replace('/', '-');
					Logger.d(TAG, name);

					inputStream.readFully(intBytes);
					int dataLen = byteArrayToInt(intBytes);
					byte[] buffer = new byte[4096];
					fileList = new ArrayList<>();
					File f = FileUtils.createFile(name);
					Logger.d(TAG, f.getAbsolutePath());
					fileList.add(f);
					FileOutputStream fos = new FileOutputStream(f);
					do {
						int readLen = inputStream.read(buffer, 0, Math.min(dataLen, buffer.length));
						fos.write(buffer, 0, readLen);
						dataLen -= readLen;
					} while (dataLen > 0);
					fos.close();
				}
			}
			response.setFiles(fileList);
			return LJResponse.parse(response);
		} catch (Exception e) {
			Logger.e(TAG, "P1Client请求时发生异常", e);
			return JSON.parseObject("{ErrMsg:\"" + getErrorMsg(e) + "\",ErrCode:\"请求异常\"}", cls);
		}
	}

	private String getErrorMsg(Exception e) {
		if (e instanceof EOFException) {
			return "SOCKET连接被中断";
		}
		if (e instanceof SocketTimeoutException) {
			return "SOCKET连接超时";
		}
		if (e instanceof SocketException) {
			return "SOCKET异常";
		}
		if (!TextUtils.isEmpty(e.getMessage())) {
			return e.getMessage().replace('\"', '\'');
		} else {
			return "";
		}
	}

	@Override
	public void release() {
		try {
			socket.shutdownInput();
			socket.shutdownOutput();
			socket.close();
		} catch (IOException e) {
			Logger.e(TAG, "P1Client关闭socket发生错误", e);
		}
	}

	@Override
	public void beat() throws IOException {
		OutputStream os = socket.getOutputStream();
		os.write(0x00);
		Logger.d(TAG, "BEAT SUCCESS");
	}

	/**
	 * int32转byte[]
	 */
	private byte[] intToByteArray(int i) {
		byte[] result = new byte[4];
		result[3] = (byte) ((i >> 24) & 0xFF);
		result[2] = (byte) ((i >> 16) & 0xFF);
		result[1] = (byte) ((i >> 8) & 0xFF);
		result[0] = (byte) (i & 0xFF);
		return result;
	}

	/**
	 * short转byte[]
	 */
	private byte[] shortToByteArray(int i) {
		byte[] result = new byte[2];
		result[1] = (byte) ((i >> 8) & 0xFF);
		result[0] = (byte) (i & 0xFF);
		return result;
	}

	private int byteArrayToInt(byte[] b) {
		int value = 0;
		for (int i = 0; i < 4; i++) {
			int shift = i * 8;
			value += (b[i] & 0x000000FF) << shift;//往高位游
		}
		return value;
	}

}
